#include "skftest.h"


#include <dlfcn.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <string_view>
#include <assert.h>
#include <vector>
#include <memory>

#include "skf/skf_int.h"
#include "skf/skf_ext.h"
#include "skf/skf_internal.h"
#include "openssl/testfunctions.h"

void printInfo(const char* format, ...);
void printError(const char* format, ...);


SKF_METHOD* g_skf_method = nullptr;
// TODO: maqidi, there may have more than one
// key exists at the same time
DEVHANDLE   g_skf_device = nullptr;
HAPPLICATION g_skf_application = nullptr;

namespace {
BOOL skf_list_devices(std::vector<std::string>& label) {
  BOOL is_present = TRUE;
  ULONG name_list_len = 0;

  do {
    if (g_skf_method->EnumDev(is_present, NULL, &name_list_len) != SAR_OK) {
      printError("SKF: Failed to enum device and get size of names");
      break;
    }
    if (name_list_len == 0) {
      return TRUE;
    }
    std::string name_list(name_list_len, 0);
    ULONG len = name_list.size();

    if (g_skf_method->EnumDev(is_present, (LPSTR)name_list.data(), &len) != SAR_OK) {
      printError("SKF: Failed to enum device and get names");
      break;
    }

    char* name = nullptr;
    for (name = name_list.data(), len = strlen(name);
         *name && (len != 0); name += len + 1) {
      label.emplace_back(name, len);
    }
    return TRUE;
  } while(0);

end:
  return FALSE;
}

BOOL skf_enum_applications(std::vector<std::string>& apps) {
  ULONG name_list_len = 0;

  do {
    ULONG ret = SAR_OK;
    if ((ret = g_skf_method->EnumApplication(g_skf_device, NULL,
                                             &name_list_len)) != SAR_OK) {
      printError("SKF: Failed to enum applications and get size of names"
          "  error: %lx", ret);
      return FALSE;
    }
    if (name_list_len == 0) {
      return TRUE;
    }
    std::string name_list(name_list_len, 0);
    ULONG len = name_list.size();

    if (g_skf_method->EnumApplication(g_skf_device,
                                      (LPSTR)name_list.data(), &len) != SAR_OK) {
      printError("SKF: Failed to enum applications and get names"
          "  error: %lx", ret);
      break;
    }

    char* name = nullptr;
    for (name = name_list.data(), len = strlen(name);
         *name && (len != 0); name += len + 1) {
      apps.emplace_back(name, len);
    }
    return TRUE;
  } while(0);

  return FALSE;
}

BOOL skf_list_containers(std::vector<std::string>& apps,
                           HAPPLICATION skf_handle,
                           SKF_METHOD* skf_method) {
  ULONG name_list_len = 0;

  do {
    ULONG ret = SAR_OK;
    if ((ret = g_skf_method->EnumContainer(g_skf_application, NULL,
                                           &name_list_len)) != SAR_OK) {
      printError("SKF: Failed to enum containers and get size of names %x", ret);
      return FALSE;
    }
    if (name_list_len == 0) {
      return TRUE;
    }
    std::string name_list(name_list_len, 0);
    ULONG len = name_list.size();

    if (g_skf_method->EnumContainer(g_skf_application,
                                    (LPSTR)name_list.data(), &len) != SAR_OK) {
      printError("SKF: Failed to enum containers and get names %x", ret);
      break;
    }

    char* name = nullptr;
    for (name = name_list.data(), len = strlen(name);
         *name && (len != 0); name += len + 1) {
      apps.emplace_back(name, len);
    }
    return TRUE;
  } while(0);

  return FALSE;
}

}


bool InitSDF(const std::string& lib_path) {

  do {
    if(!g_skf_method) {
      g_skf_method = SKF_METHOD_load_library(lib_path.data());
    }

    if(!g_skf_method) {
      printError("SKF: failed to load driver lib");
      break;
    }

    std::vector<std::string> labels;
    if(skf_list_devices(labels)) {
      for(auto& label: labels) {
        printInfo("label:  %s", label.data());
      }
    } else {
      printError("Error occurs when listing device");
      break;
    }

    ULONG ret = g_skf_method->ConnectDev((LPSTR)labels[0].data(), &g_skf_device);
    if(ret != SAR_OK || !g_skf_device) {
      printError("Error occurs when connecting to device %s, ERROR: %lx",
                 labels[0].data(), ret);
      break;
    }

    std::unique_ptr<DEVINFO> dev_info = std::make_unique<DEVINFO>();
    if((ret = g_skf_method->GetDevInfo(g_skf_device, dev_info.get())) != SAR_OK) {
      printError("Error occurs when get device info %s, ERROR: %lx",
                 labels[0].data(), ret);
      break;
    }

    printInfo("Device Connected, manufacturer: %s, issuer: %s\n",
              dev_info->Manufacturer, dev_info->Issuer);

    std::vector<std::string> apps;

    if(skf_enum_applications(apps)) {
      for(auto& app: apps) {
        printInfo("app:  %s", app.data());
      }
    } else {
      printError("Error occurs when enum applications");
      break;
    }

    ret = g_skf_method->OpenApplication(g_skf_device,
                                        (LPSTR)apps[0].data(),
                                        &g_skf_application);

    // open container
    HCONTAINER skf_container = nullptr;
    {
      std::vector<std::string> containers;
      if(skf_list_containers(containers, g_skf_application, g_skf_method)) {
          for(auto& container: containers) {
            printInfo( "container:  %s", container.data());
          }
      } else {
          printError( "Error occurs when list containers");
          break;
      }
      ret = g_skf_method->OpenContainer(g_skf_application,
              (LPSTR)containers[0].data(),
              &skf_container);
      if(ret != SAR_OK || !skf_container) {
          printError("Error occurs when opening application %s, Error:  %x",
                    containers[0].data(), ret);
          break;
      }
    }

    ULONG cert_len = 0;
    ret = g_skf_method->ExportPublicKey(skf_container, TRUE, NULL, &cert_len);
    if(SAR_OK != ret || cert_len == 0) {
      printError( "There is no cert in container, export cert faild! ");
      break;
    }

    //std::vector<uint8_t> cert_bytes(cert_len+1, 0);
    std::vector<uint8_t> cert_bytes(cert_len, 0);
    ret = g_skf_method->ExportPublicKey(skf_container, TRUE, cert_bytes.data(), &cert_len);

    std::string public_key_str = std::move(Hex2Ascii(cert_bytes.data(), cert_len));
    printInfo("public key: %s", public_key_str.data());


    // std::string data_to_encrypt("hello, world");
    // ULONG ulInLen=32;
    // ECCCIPHERBLOB *pCipherText = (ECCCIPHERBLOB *)malloc(sizeof(ECCCIPHERBLOB)+ulInLen);
    // pCipherText->CipherLen = ulInLen;

    // ret = g_skf_method->ExtECCEncrypt(g_skf_device,
    //         (ECCPUBLICKEYBLOB *)cert_bytes.data(),
    //         (BYTE *)data_to_encrypt.data(),ulInLen,pCipherText);
    // if(SAR_OK != ret) {
    //   printError( "SKF_ExtECCEncrypt faild %x ", ret);
    //   break;
    // }

    // std::string cipher_text_hex(pCipherText->CipherLen * 2, 0);
    // for(int i = 0; i < pCipherText->CipherLen; i++) {
    //   sprintf(cipher_text_hex.data() + i *2, "%x", pCipherText->Cipher[i]);
    // }

    // printInfo("encrypt data: %s", cipher_text_hex.data());


    // digest
    unsigned char puc_id[32] ={0};
    ULONG puc_id_len = 16, digest_len = 64;
    HANDLE hash_handle;
    memcpy(puc_id,"1234567812345678",16);
    ret = g_skf_method->DigestInit(g_skf_device,
            SGD_SM3, (ECCPUBLICKEYBLOB* )cert_bytes.data(), puc_id, puc_id_len, &hash_handle);
    if (ret != SAR_OK) {
      printError("DigestInit failed, Error: %x", ret);
      break;
    }

    BYTE digest_data[64] = {0};
    std::string plain_text_input("psp verify");
    ret = g_skf_method->Digest(hash_handle,
            (BYTE*)plain_text_input.data(),
            plain_text_input.size(),
            digest_data,
            &digest_len);

    if (ret != SAR_OK) {
      printError("Digest failed, Error: %x", ret);
      break;
    }

    std::string digest_data_str(std::move(Hex2Ascii(digest_data, digest_len)));
    printInfo("Digest Result: %s", Hex2Ascii(digest_data, digest_len).data());

    ULONG retry_count = 2;
    ret = g_skf_method->VerifyPIN(g_skf_application, USER_TYPE,
            (LPSTR)"123123123", &retry_count);
    if (ret != SAR_OK) {
      printError("VerifyPIN failed, Error: %x", ret);
      break;
    }


    ECCSIGNATUREBLOB* sm2_sig = new ECCSIGNATUREBLOB;

    ret =g_skf_method->ECCSignData(
        skf_container,
        digest_data, digest_len,
        sm2_sig
    );

    printInfo("ECCSignData Result: %s", Hex2Ascii((BYTE*)sm2_sig, sizeof(ECCSIGNATUREBLOB)).data());

    std::vector<uint8_t> digest_data_reserved;
    Ascii2Hex(digest_data_str, digest_data_reserved);
    // std::string digest_data_str(std::move(Hex2Ascii(digest_data, digest_len)));


    const char kPublicKey[] =
            "000100000000000000000000000000000000000000000000000000000000000000000000"
            "8daf4ecf552354bf12670e97c6f5c1abb6d16adeab64663ee2f61d3af3133188"
            "0000000000000000000000000000000000000000000000000000000000000000"
            "b3a19483bb2648677fef6b05c5c928fcbd79970cd8fd96b396c0f113b8c3aae4";

    std::vector<uint8_t> public_key;
    Ascii2Hex(kPublicKey, public_key);

    ret =g_skf_method->ECCVerify(
        g_skf_device,
        (ECCPUBLICKEYBLOB* )cert_bytes.data(),
        // (ECCPUBLICKEYBLOB* )public_key.data(),
        digest_data_reserved.data(), digest_data_reserved.size(),
        sm2_sig
    );

    if(SAR_OK != ret) {
      printError( "ECCVerify faild %x ", ret);
      break;
    } else {
      printInfo( "ECCVerify success ");
    }

//     std::string plain_text(100, 0);
//     ULONG plain_text_len = plain_text.size();
// //     // 执行解密
// //     ret = g_skf_method->ECCDecrypt(
// //         skf_container,     // [in] 容器句柄
// //         // SGD_SM2,        // [in] 算法标识
// //         pCipherText,   // [in] 密文数据
// // //        pCipherText->CipherLen,    // [in] 密文长度
// //         (BYTE *)plain_text.data(),    // [out] 明文缓冲区
// //         &plain_text_len     // [in/out] 输入缓冲区大小，输出实际长度
// //     );

//     if(SAR_OK != ret) {
//       printError( "Decrypt faild %x ", ret);
//       break;
//     }

//     printInfo("Decrypt plain text: %s", plain_text.data());

    return true;

  } while(0);

  UnInitSDF();
  return false;
}

void UnInitSDF() {
  if(g_skf_method) {
    SKF_METHOD_free(g_skf_method);
    g_skf_method = nullptr;
  }
}
